package org.se.reflect;

import cn.hutool.core.util.ReflectUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

/**
 * Java SE 原生反射 API
 * <p>
 * 1、若已知具体的类，通过类的 class 属性获取，该方式最安全可靠，程序性能最高，如：Class clazz = java.util.Date.class;
 * 2、若已知某个类的实例，调用该实例的 getClass() 方法获取 Class 对象，如：
 * Date date = new java.util.Date();
 * Class clazz = date.getClass();
 * 3、若已知一个类的全类名，且该类在类路径下，可通过 Class 类的静态方法 forName() 获取，可能抛出 ClassNotFoundException，如：
 * try {
 * Class clazz = Class.forName("java.util.Date");
 * } catch (ClassNotFoundException e) {
 * e.printStackTrace();
 * }
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/11/13 10:53
 */
public class ReflectTest {

    /**
     * 获取指定类名 name 的 Class 对象，如果找不到类，则ClassNotFoundException
     * Class<?> forName(String className)
     * 调用缺省构造函数，返回该 Class 对象的一个实例, 此时 class 对象必须有默认的无参构造，否则创建对象失败
     * T newInstance()
     */
    @Test
    public void testClassForName() {
        try {
            //类的全路径
            String reference = "org.se.reflect.Person";
            //返回指定类名 name 的 Class 对象，如果找不到类，则ClassNotFoundException
            Class clazz = Class.forName(reference);
            //调用缺省构造函数，返回该 Class 对象的一个实例, 此时 class 对象必须有默认的无参构造，否则创建对象失败
            Object obj = clazz.newInstance();
            // 返回此Class对象表示的类或接口的指定的public的Field, 属性如果是 private 修饰，则会抛出 NoSuchFieldException 异常。
            // 属性没有提供  getter 、setter 方法也不影响赋值取值
            Field pNameField = clazz.getField("pName");
            Field pIdField = clazz.getField("pId");
            pIdField.set(obj, 9527);
            pNameField.set(obj, "华安");
            //Person{pId=9527, pName='华安'}
            System.out.println(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过指定构造器参数类型获取类的构造器，然后使用 Constructor 的 newInstance 方法传入参数值创建实例
     * Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
     * 调用指定参数的构造函数，返回该 Class 对象的一个实例
     * T newInstance(Object ... initargs)
     */
    @Test
    public void testDeclaredConstructor() {
        try {
            Class clazz = Person.class;
            //先通过指定构造器参数类型获取类的构造器，然后使用 Constructor 的 newInstance 方法传入参数值创建实例
            Constructor<Person> constructors = clazz.getDeclaredConstructor(Integer.class, String.class);
            Person person = constructors.newInstance(1000, "张无忌");
            //Person{pId=1000, pName='张无忌'}
            System.out.println(person);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 源类.class.isAssignableFrom(目标类、子类或接口实现类.class)
     * 用于判断 '参数类' 是否是 '源类' 的子类、接口实现类，或者与 '源类' 相同，在此情况下返回 true.
     */
    @Test
    public void testIsAssignableFrom() {
        Class string = java.lang.String.class;
        Class object = java.lang.Object.class;
        Class<Person> personClass = Person.class;
        Class<Serializable> serializableClass = Serializable.class;

        //Object 是 String 父类? true
        System.out.println("Object 是 String 父类? " + object.isAssignableFrom(string));
        //Object 与 Object 相同? true
        System.out.println("Object 与 Object 相同? " + object.isAssignableFrom(object));
        //String 是 Object 父类? false
        System.out.println("String 是 Object 父类? " + string.isAssignableFrom(object));

        boolean assignableFrom = serializableClass.isAssignableFrom(personClass);
        //org.se.reflect.Person 实体类是否实现了 java.io.Serializable? true
        System.out.println(personClass.getName() + " 实体类是否实现了 " + serializableClass.getName() + "? " + assignableFrom);
    }

    /**
     * 反射之 Method 使用演示
     * Method getMethod(String name, Class<?>... parameterTypes): 获取类中指定的方法
     * Object invoke(Object obj, Object... args): 调用对象的方法
     * void setAccessible(boolean flag)：对于私有方法，调用前必须设置访问权限，否则抛异常：IllegalAccessException
     */
    @Test
    public void testMethod1() {
        try {
            Class clazz = PersonService.class;
            //获取类的对象
            Object obj = clazz.newInstance();
            //获取类中指定的方法
            Method method = clazz.getMethod("deleteAll");
            //调用对象的方法，目标方法 "deleteAll" 会输出:delete all data
            method.invoke(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Class clazz = Class.forName("org.se.reflect.PersonService");
            //获取类的实例
            Object instance = clazz.newInstance();
            //getMethod(String name, Class<?>... parameterTypes)：获取类中指定的方法
            //name 表示方法名称，parameterTypes 为方法的参数类型
            Method method = clazz.getMethod("findById", Integer.class);
            //Object invoke(Object obj, Object... args):调用对象的方法，并传入参数值
            //obj 为调用方法所在的对象，args 为方法的参数值
            Object result = method.invoke(instance, 200);
            //输出：Person{pId=200, pName='阿苏勒_200'}
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Class clazz = PersonService.class;
            //获取类的对象
            Object obj = clazz.newInstance();
            //获取类中指定的方法。getMethod 只能取 public 修饰的方法，其它修饰类型的方法必须使用 getDeclaredMethod
            Method method = clazz.getDeclaredMethod("deleteById", Integer.class);
            //对于私有方法，调用前必须设置访问权限，否则抛异常：IllegalAccessException
            method.setAccessible(true);
            //调用对象的方法，目标方法 "deleteById" 会输出:deletes id = 1001
            method.invoke(obj, 1001);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回此 Class 对象表示的类或接口的指定的 Field。继承的属性不能获取
     * Field getDeclaredField(String name)
     */
    @Test
    public void testField() {
        try {
            Class clazz = Student.class;
            Object instance = clazz.newInstance();

            Field studentIDField = clazz.getDeclaredField("studentID");
            Field classNumberField = clazz.getDeclaredField("classNumber");
            Field homeAddressField = clazz.getDeclaredField("homeAddress");

            //set之前必须先让属性对外可见
            studentIDField.setAccessible(true);
            classNumberField.setAccessible(true);
            homeAddressField.setAccessible(true);
            studentIDField.set(instance, 1000);
            classNumberField.set(instance, 1810);
            homeAddressField.set(instance, "芙蓉区万家丽");

            //Student{studentID=1000, classNumber=1810, homeAddress='芙蓉区万家丽'}
            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 泛型与反射
     *
     * @param tClass
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> T getOne(Class<T> tClass) throws Exception {
        //先通过类型获取到对象
        T obj = ReflectUtil.newInstanceIfPossible(tClass);
        //为对象设置属性，比如这是数据是查询数据库来的
        BeanUtils.setProperty(obj, "pId", "P1000F");
        BeanUtils.setProperty(obj, "pName", "张三");
        BeanUtils.setProperty(obj, "age", 85);
        BeanUtils.setProperty(obj, "birthday", new Date());
        return obj;
    }

    @Test
    public void testGetOne() throws Exception {
        Map<String, Object> one = getOne(Map.class);
        //{birthday=Thu Dec 09 18:18:46 CST 2021, pName=张三, pId=P1000F, age=85}
        System.out.println(one);

        //Person{pId=0, pName='张三'}
        Person person = getOne(Person.class);
        System.out.println(person);
    }


}
